// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "components/subresource_filter/core/common/indexed_ruleset.h"

#include "base/check.h"
#include "base/metrics/histogram_macros.h"
#include "base/trace_event/trace_event.h"
#include "components/subresource_filter/core/common/first_party_origin.h"
#include "url/gurl.h"
#include "url/origin.h"

#ifdef OHOS_ARKWEB_ADBLOCK
#include "base/logging.h"
#endif  // OHOS_ARKWEB_ADBLOCK

namespace subresource_filter {

namespace {
namespace proto = url_pattern_index::proto;
using FindRuleStrategy =
    url_pattern_index::UrlPatternIndexMatcher::FindRuleStrategy;
using EmbedderConditionsMatcher =
    url_pattern_index::UrlPatternIndexMatcher::EmbedderConditionsMatcher;

// A helper function to get the checksum on a data buffer.
int LocalGetChecksum(const uint8_t* data, size_t size) {
  uint32_t hash = base::PersistentHash(data, size);

  // Strip off the sign bit since this needs to bepersisted in preferences
  // which don't support unsigned ints.
  return static_cast<int>(hash & 0x7fffffff);
}

VerifyStatus GetVerifyStatus(const uint8_t* buffer,
                             size_t size,
                             int expected_checksum) {
  // TODO(ericrobinson): Remove the verifier once we've updated the ruleset at
  // least once.  The verifier detects a subset of the errors detected by the
  // checksum, and is unneeded once expected_checksum is consistently nonzero.
  flatbuffers::Verifier verifier(buffer, size);
  if (expected_checksum != 0 &&
      expected_checksum != LocalGetChecksum(buffer, size)) {
    return flat::VerifyIndexedRulesetBuffer(verifier)
               ? VerifyStatus::kChecksumFailVerifierPass
               : VerifyStatus::kChecksumFailVerifierFail;
  }
  if (!flat::VerifyIndexedRulesetBuffer(verifier)) {
    return expected_checksum == 0 ? VerifyStatus::kVerifierFailChecksumZero
                                  : VerifyStatus::kVerifierFailChecksumPass;
  }
  return expected_checksum == 0 ? VerifyStatus::kPassChecksumZero
                                : VerifyStatus::kPassValidChecksum;
}

}  // namespace

// RulesetIndexer --------------------------------------------------------------

#ifdef OHOS_ARKWEB_ADBLOCK
const int RulesetIndexer::kIndexedFormatVersion = 36;
#else
const int RulesetIndexer::kIndexedFormatVersion = 36;
#endif

// This static assert is meant to catch cases where
// url_pattern_index::kUrlPatternIndexFormatVersion is incremented without
// updating RulesetIndexer::kIndexedFormatVersion.
#ifdef OHOS_ARKWEB_ADBLOCK
static_assert(url_pattern_index::kUrlPatternIndexFormatVersion == 15,
              "kUrlPatternIndexFormatVersion has changed, make sure you've "
              "also updated RulesetIndexer::kIndexedFormatVersion above.");
#else
static_assert(url_pattern_index::kUrlPatternIndexFormatVersion == 15,
              "kUrlPatternIndexFormatVersion has changed, make sure you've "
              "also updated RulesetIndexer::kIndexedFormatVersion above.");
#endif

#ifdef OHOS_ARKWEB_ADBLOCK
// This static assert is meant to catch cases where
// url_pattern_index:：kCssPatternIndexFormatVersion is incremented without
// updating RulesetIndexer::kIndexedFormatVersion.
static_assert(url_pattern_index::kCssPatternIndexFormatVersion == 1,
              "kCssPatternIndexFormatVersion has changed, make sure you've "
              "also updated RulesetIndexer::kIndexedFormatVersion above.");
#endif  // OHOS_ARKWEB_ADBLOCK

#ifdef OHOS_ARKWEB_ADBLOCK
RulesetIndexer::RulesetIndexer()
    : blocklist_(&builder_),
      allowlist_(&builder_),
      deactivation_(&builder_),
      css_blocklist_(&builder_),
      css_allowlist_(&builder_) {}
#else
RulesetIndexer::RulesetIndexer()
    : blocklist_(&builder_), allowlist_(&builder_), deactivation_(&builder_) {}
#endif  // OHOS_ARKWEB_ADBLOCK

RulesetIndexer::~RulesetIndexer() = default;

bool RulesetIndexer::AddUrlRule(const proto::UrlRule& rule) {
  const auto offset =
      url_pattern_index::SerializeUrlRule(rule, &builder_, &domain_map_);
  // Note: A zero offset.o means a "nullptr" offset. It is returned when the
  // rule has not been serialized.
  if (!offset.o)
    return false;

  if (rule.semantics() == proto::RULE_SEMANTICS_BLOCKLIST) {
    blocklist_.IndexUrlRule(offset);
  } else {
    const auto* flat_rule = flatbuffers::GetTemporaryPointer(builder_, offset);
    DCHECK(flat_rule);
    if (flat_rule->element_types())
      allowlist_.IndexUrlRule(offset);
    if (flat_rule->activation_types())
      deactivation_.IndexUrlRule(offset);
  }

  return true;
}

#ifdef OHOS_ARKWEB_ADBLOCK

bool RulesetIndexer::AddCssRule(const proto::CssRule& rule) {
  auto offset =
      url_pattern_index::SerializeCssRule(rule, &builder_, &domain_map_);
  // Note:A zero offset.o means a "nullptr"offset. It is returned when the
  // rule has not been serialized.
  if (!offset.o) {
    LOG(ERROR) << "[AdBlock] Fail to serialize css rule:"
               << rule.css_selector();
    return false;
  }

  if (rule.semantics() == proto::RULE_SEMANTICS_BLOCKLIST) {
    css_blocklist_.IndexCssRule(offset);
  } else {
    css_allowlist_.IndexCssRule(offset);
  }

  return true;
}
#endif  // OHOS_ARKWEB_ADBLOCK

void RulesetIndexer::Finish() {
  auto blocklist_offset = blocklist_.Finish();
  auto allowlist_offset = allowlist_.Finish();
  auto deactivation_offset = deactivation_.Finish();

#ifdef OHOS_ARKWEB_ADBLOCK
  auto css_blocklist_offset = css_blocklist_.Finish();
  auto css_allowlist_offset = css_allowlist_.Finish();
  LOG(INFO) << "[AdBlock] url whitelist fallback_rule size:"
            << allowlist_.GetFallbackRuleListSize();
  LOG(INFO) << "[AdBlock] url whitelist hash table size:"
            << allowlist_.GetNGramHashTableSize();
  LOG(INFO) << "[AdBlock] url blacklist fallback rule.size:"
            << blocklist_.GetFallbackRuleListSize();
  LOG(INFO) << "[AdBlock] url blacklist hash table size:"
            << blocklist_.GetNGramHashTableSize();
  LOG(INFO) << "[AdBlock] deactivation fallback rule size:"
            << deactivation_.GetFallbackRuleListSize();
  LOG(INFO) << "[AdBlock] deactivation hash table size:"
            << deactivation_.GetNGramHashTableSize();
  LOG(INFO) << "[AdBlock] css whitelist fallback rule size:"
            << css_allowlist_.GetFallbackRuleListSize();
  LOG(INFO) << "[AdBlock] css whitelist hash table size:"
            << css_allowlist_.GetNGramHashTableSize();
  LOG(INFO) << "[AdBlock] css whitelist no domain rule size:"
            << css_allowlist_.GetNoDomainRuleListSize();
  LOG(INFO) << "[AdBlock] css_blacklist fallback rule size:"
            << css_blocklist_.GetFallbackRuleListSize();
  LOG(INFO) << "[AdBlock] css_blacklist hash table size:"
            << css_blocklist_.GetNGramHashTableSize();
  LOG(INFO) << "[AdBlock] css_blacklist no domain rule size:"
            << css_blocklist_.GetNoDomainRuleListSize();
  LOG(INFO) << "[AdBlock] css_blacklist hash table actual used:"
            << css_blocklist_.GetNGramHashTableUsedSize();
  // LOG(INFO) << "[AdBlock] easylist version_in indexed ruleset = "
  //           << easylist_version_;
  auto url_rules_index_offset = flat::CreateIndexedRuleset(
      builder_, blocklist_offset, allowlist_offset, deactivation_offset,
      css_blocklist_offset, css_allowlist_offset);
#else
  auto url_rules_index_offset = flat::CreateIndexedRuleset(
      builder_, blocklist_offset, allowlist_offset, deactivation_offset);
#endif  // OHOS_ARKWEB_ADBLOCK
  builder_.Finish(url_rules_index_offset);
}

int RulesetIndexer::GetChecksum() const {
  return LocalGetChecksum(data(), size());
}

// IndexedRulesetMatcher -------------------------------------------------------

// static
bool IndexedRulesetMatcher::Verify(const uint8_t* buffer,
                                   size_t size,
                                   int expected_checksum) {
  TRACE_EVENT_BEGIN1(TRACE_DISABLED_BY_DEFAULT("loading"),
                     "IndexedRulesetMatcher::Verify", "size", size);
  SCOPED_UMA_HISTOGRAM_TIMER(
      "SubresourceFilter.IndexRuleset.Verify2.WallDuration");
  VerifyStatus status = GetVerifyStatus(buffer, size, expected_checksum);
  UMA_HISTOGRAM_ENUMERATION("SubresourceFilter.IndexRuleset.Verify.Status",
                            status);
  TRACE_EVENT_END1(TRACE_DISABLED_BY_DEFAULT("loading"),
                   "IndexedRulesetMatcher::Verify", "status",
                   static_cast<int>(status));
  return status == VerifyStatus::kPassValidChecksum ||
         status == VerifyStatus::kPassChecksumZero;
}

#ifdef OHOS_ARKWEB_ADBLOCK
IndexedRulesetMatcher::~IndexedRulesetMatcher() = default;

IndexedRulesetMatcher::IndexedRulesetMatcher(const uint8_t* buffer, size_t size)
    : root_(flat::GetIndexedRuleset(buffer)),
      blocklist_(root_->blocklist_index()),
      allowlist_(root_->allowlist_index()),
      deactivation_(root_->deactivation_index()),
      css_blocklist_(root_->css_blocklist_index()),
      css_allowlist_(root_->css_allowlist_index()) {}
#else
IndexedRulesetMatcher::IndexedRulesetMatcher(const uint8_t* buffer, size_t size)
    : root_(flat::GetIndexedRuleset(buffer)),
      blocklist_(root_->blocklist_index()),
      allowlist_(root_->allowlist_index()),
      deactivation_(root_->deactivation_index()) {}
#endif

bool IndexedRulesetMatcher::ShouldDisableFilteringForDocument(
    const GURL& document_url,
    const url::Origin& parent_document_origin,
    proto::ActivationType activation_type) const {
  return !!deactivation_.FindMatch(
      document_url, parent_document_origin, proto::ELEMENT_TYPE_UNSPECIFIED,
      activation_type,
      FirstPartyOrigin::IsThirdParty(document_url, parent_document_origin),
      false, EmbedderConditionsMatcher(), FindRuleStrategy::kAny,
      {} /* disabled_rule_ids */);
}

LoadPolicy IndexedRulesetMatcher::GetLoadPolicyForResourceLoad(
    const GURL& url,
    const FirstPartyOrigin& first_party,
    proto::ElementType element_type,
    bool disable_generic_rules) const {
  const url_pattern_index::flat::UrlRule* rule =
      MatchedUrlRule(url, first_party, element_type, disable_generic_rules);

  if (!rule)
    return LoadPolicy::ALLOW;

  return rule->options() & url_pattern_index::flat::OptionFlag_IS_ALLOWLIST
             ? LoadPolicy::EXPLICITLY_ALLOW
             : LoadPolicy::DISALLOW;
}

const url_pattern_index::flat::UrlRule* IndexedRulesetMatcher::MatchedUrlRule(
    const GURL& url,
    const FirstPartyOrigin& first_party,
    url_pattern_index::proto::ElementType element_type,
    bool disable_generic_rules) const {
  const bool is_third_party = first_party.IsThirdParty(url);
  const EmbedderConditionsMatcher embedder_conditions_matcher;

  auto find_match =
      [&](const url_pattern_index::UrlPatternIndexMatcher& matcher) {
        return matcher.FindMatch(
            url, first_party.origin(), element_type,
            proto::ACTIVATION_TYPE_UNSPECIFIED, is_third_party,
            disable_generic_rules, embedder_conditions_matcher,
            FindRuleStrategy::kAny, {} /* disabled_rule_ids */);
      };

  // Always check the allowlist for subdocuments. For other forms of resources,
  // it is not necessary to differentiate between the resource not matching a
  // blocklist rule and matching an allowlist rule. For subdocuments, matching
  // an allowlist rule can still override ad tagging decisions even if the
  // subdocument_url did not match a blocklist rule.
  //
  // To optimize the subdocument case, we only check the blocklist if an
  // allowlist rule was not matched.
  if (element_type == proto::ELEMENT_TYPE_SUBDOCUMENT) {
    auto* allowlist_rule = find_match(allowlist_);
    if (allowlist_rule)
      return allowlist_rule;
    return find_match(blocklist_);
  }

  // For non-subdocument elements, only check the allowlist if there is a
  // matched blocklist rule to prevent_unnecessary lookups.
  auto* blocklist_rule = find_match(blocklist_);
  if (!blocklist_rule)
    return nullptr;
  auto* allowlist_rule = find_match(allowlist_);
  return allowlist_rule ? allowlist_rule : blocklist_rule;
}

#ifdef OHOS_ARKWEB_ADBLOCK
std::unique_ptr<const std::vector<const url_pattern_index::flat::CssRule*>>
IndexedRulesetMatcher::MatchedCssRule(const GURL& document_url,
                                      bool disable_generic_rules) const {
  std::vector<const url_pattern_index::flat::CssRule*> blocklist_rules =
      css_blocklist_.FindMatchInSpecialRules(document_url);

  std::vector<const url_pattern_index::flat::CssRule*> allowlist_rules =
      css_allowlist_.FindMatchInSpecialRules(document_url);

  std::unique_ptr<std::vector<const url_pattern_index::flat::CssRule*>>
      match_result = std::make_unique<
          std::vector<const url_pattern_index::flat::CssRule*>>();

  for (auto* blocklist_rule : blocklist_rules) {
    bool is_hit_whitelist = false;
    for (auto* allowlist_rule : allowlist_rules) {
      if (!blocklist_rule || !allowlist_rule) {
        continue;
      }

      if (!(blocklist_rule->css_selector()) ||
          !(allowlist_rule->css_selector())) {
        continue;
      }

      if (blocklist_rule->css_selector()->str() ==
          allowlist_rule->css_selector()->str()) {
        is_hit_whitelist = true;
        break;
      }
    }

    if (!is_hit_whitelist) {
      match_result->push_back(blocklist_rule);
    }
  }

  if (!disable_generic_rules) {
    const url_pattern_index::FlatCssRuleList* generic_blocklist_rules =
        css_blocklist_.GetGenericCssRules();

    if (generic_blocklist_rules) {
      for (auto* blocklist_rule : *generic_blocklist_rules) {
        bool is_hit_whitelist = false;
        for (auto* allowlist_rule : allowlist_rules) {
          if (!blocklist_rule || !allowlist_rule) {
            continue;
          }

          if (!(blocklist_rule->css_selector()) ||
              !(allowlist_rule->css_selector())) {
            continue;
          }

          if (blocklist_rule->css_selector()->str() ==
              allowlist_rule->css_selector()->str()) {
            is_hit_whitelist = true;
            break;
          }
        }
        if (!is_hit_whitelist) {
          match_result->push_back(blocklist_rule);
        }
      }
    }
  }

  return match_result;
}

bool IndexedRulesetMatcher::HasGenericHideOption(
    const GURL& document_url,
    const url::Origin& parent_document_origin) const {
  return !!deactivation_.FindMatch(
      document_url, parent_document_origin, proto::ELEMENT_TYPE_UNSPECIFIED,
      url_pattern_index::proto::ACTIVATION_TYPE_GENERICHIDE,
      FirstPartyOrigin::IsThirdParty(document_url, parent_document_origin),
      false, EmbedderConditionsMatcher(), FindRuleStrategy::kAny,
      {} /* disabled_rule_ids */);
}

bool IndexedRulesetMatcher::HasElemHideOption(
    const GURL& document_url,
    const url::Origin& parent_document_origin) const {
  return !!deactivation_.FindMatch(
      document_url, parent_document_origin, proto::ELEMENT_TYPE_UNSPECIFIED,
      url_pattern_index::proto::ACTIVATION_TYPE_ELEMHIDE,
      FirstPartyOrigin::IsThirdParty(document_url, parent_document_origin),
      false, EmbedderConditionsMatcher(), FindRuleStrategy::kAny,
      {} /* disabled rule ids */);
}

bool IndexedRulesetMatcher::HasDocumentOption(
    const GURL& document_url,
    const url::Origin& parent_document_origin) const {
  return !!deactivation_.FindMatch(
      document_url, parent_document_origin, proto::ELEMENT_TYPE_UNSPECIFIED,
      url_pattern_index::proto::ACTIVATION_TYPE_DOCUMENT,
      FirstPartyOrigin::IsThirdParty(document_url, parent_document_origin),
      false, EmbedderConditionsMatcher(), FindRuleStrategy::kAny,
      {} /* disabled_rule_ids */);
}
#endif  // OHOS_ARKWEB_ADBLOCK

}  // namespace subresource_filter
